collision
Daddy told me about cool MD5 hash collision today.
I wanna do something like that too!
File properties
Let's check the nature of our challenge file.
col@ubuntu:~$ file ./col
./col: setgid ELF 32-bit LSB pie executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=48d83f055c56d12dc4762db539bf8840e5b4f6cc, for GNU/Linux 3.2.0, not stripped
We can see that it is a little-endian 32-bit ELF executable.
Source code
#include <stdio.h>
#include <string.h>
unsigned long hashcode = 0x21DD09EC;
unsigned long check_password(const char* p){
int* ip = (int*)p;
int i;
int res=0;
for(i=0; i<5; i++){
res += ip[i];
}
return res;
}
int main(int argc, char* argv[]){
if(argc<2){
printf("usage : %s [passcode]\n", argv[0]);
return 0;
}
if(strlen(argv[1]) != 20){
printf("passcode length should be 20 bytes\n");
return 0;
}
if(hashcode == check_password( argv[1] )){
setregid(getegid(), getegid());
system("/bin/cat flag");
return 0;
}
else
printf("wrong passcode.\n");
return 0;
}
The challenge expects users to enter a passcode of lenght 20 bytes.
The the passcode indirectly is compared to hashcode
using the check_password()
function.
check_password()
# --- snip ---
unsigned long check_password(const char* p){
int* ip = (int*)p;
# --- snip ---
This function takes the char
pointer p
and casts it to an int
pointer.
It then assigns this int
pointer to ip
.
Let's understand what this means.
Pointer casting
When the pointer p is initialized, it acts as char
pointer and points to a character, which is 1 byte long.
+------+------+------+------+ ... (total 20 bytes)
| 0x41 | 0x42 | 0x43 | 0x44 | ... ('A', 'B', 'C', 'D', ...)
+------+------+------+------+
^^^^^^^^
|
char* p
After casting p
into an int*
, it points to integer, which is 4 bytes long.
+------+------+------+------+ ... (total 20 bytes)
| 0x41 | 0x42 | 0x43 | 0x44 | ... ('A', 'B', 'C', 'D', ...)
+------+------+------+------+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
(int*)p
+------------+------------+------------+ ...
| 0x44434241 | 0x48474645 | 0x4C4B4A49 | ... (on little-endian systems)
+------------+------------+------------+
^^^^^^^^^^^^^^
|
(int*)p
Next the check_password()
function adds all the 5 integers now pointed to by the ip
pointer.
# --- snip ----
int i;
int res=0;
for(i=0; i<5; i++){
res += ip[i];
}
return res;
# --- snip ----
This sum has to be equal to the hashcode
which is 0x21DD09EC
for us to get the flag.
Should be easy right?
We can just pass \xEC\x09\xDD\x21
+ \x00
*16 and that will cause to sum to be equal to the hashcode
.
Input:
0xEC09DD2100000000000000000000000000000000
+------------+------------+------------+------------+------------+
| 0xEC09DD21 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 |
+------------+------------+------------+------------+------------+
0xEC09DD21
+ 0x00000000
+ 0x00000000
+ 0x00000000
+ 0x00000000
--------------
0xEC09DD21
Note that we are passing \xEC\x09\xDD\x21
because the challenge is in little-endian, so we have to flip the bytes.
Let's try this solution.
col@ubuntu:~$ ./col "$(python3 -c 'import sys; sys.stdout.buffer.write(b"\xEC\x09\xDD\x21" + b"\x00"*16)')"
-bash: warning: command substitution: ignored null byte in input
passcode length should be 20 bytes
The program tells us that our input is not 20 bytes long, and that it ignored the null byte in our input.
This is because when we append \x00
to our string, we essentially terminate it.
Therefore, our input length is registered as 4.
Actual Input:
0xEC09DD21
In order to get around this, we can use append 16 \x01
bytes instead. However, we first have to modify our original input accordingly
Input:
0x????????01010101010101010101010101010101
+------------+------------+------------+------------+------------+
| 0x???????? | 0x01010101 | 0x01010101 | 0x01010101 | 0x01010101 |
+------------+------------+------------+------------+------------+
0x????????
+ 0x01010101
+ 0x01010101
+ 0x01010101
+ 0x01010101
--------------
0xEC09DD21
Let's use Python to calculate our input.
>>> target = 0xEC09DD21
>>> value = 0x01010101
>>> x = target - (4 * value)
>>> print(f"Calculated value: 0x{x:08X}")
Calculated value: 0xE805D91D
Let's craft and send our final payload.
col@ubuntu:~$ ./col "$(python3 -c 'import sys; sys.stdout.buffer.write(b"\xE8\x05\xD9\x1D" + b"\x01"*16)')"
Two_hash_collision_Nicely